package me.sapling.view;

import me.sapling.model.PageContent;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

/**
 * @author sapling
 */
public abstract class AbstractPanel extends JPanel {

    private static final int WIDTH = 800; // 画布宽度
	private static final int HEIGHT = 480; // 画布高度

	protected PageContent[] pages;
	protected PageContent[] contents;
	protected PageContent[] contentCurrent;
	protected PageContent[] contentOut;
	protected PageContent[][] contentHistory;
	private int index = 0; // 画表格时的计数索引
	protected PageContent[][] contentRouter; // 内存中每一块的拥有者
	protected int contentIndex = 0; // 用于记录router行进到第几次

	protected boolean[] reserve;
	protected int reserveCount = 0;
	private boolean finished = false;
	private boolean tableLoaded = false; // 标识内存表是否加载完毕
	private boolean memoryLoaded = false;
	private boolean contentLoaded = false;

	protected int pageNumInMemory = 3;
	protected int perUnitWidth = 30;
	protected int perUnitHeight = 30;
	protected int recordTablePositionX;
	protected int recordTablePositionY;

	private int animationX = 0, animationY = 0;
	private float animation; // 申请序列的动画的横坐标
	private int alphaStorage = 0;
	private int alphaContent = 0;
	private int alphaHistory = 0;
	private int red = 0;

	private Timer tableLoadTimer;
	private Timer memoryLoadTimer;
	private Timer contentLoadTimer;
	private Timer walkTimer;
	private Timer waitTimer;
	private Timer waitTimer2;

	public AbstractPanel(PageContent[] pages, PageContent[] contents) {
		this.setBackground(Color.WHITE);
		this.pages = pages;
		this.contents = contents;
		this.setPreferredSize(new Dimension(WIDTH, HEIGHT));
		positionInit();
		pagesInit();
		this.contentCurrent = new PageContent[pageNumInMemory];
		this.contentOut = new PageContent[contents.length];
		this.reserve = new boolean[contents.length];
		this.contentHistory = new PageContent[contents.length][pageNumInMemory];
		this.contentRouter = new PageContent[contents.length][pageNumInMemory];
		routerInit();

		this.walkTimer = new Timer(10, new WalkTimer());
		this.waitTimer = new Timer(30, new WaitTimer());
		this.waitTimer2 = new Timer(30, new WaitTimer2());
		this.memoryLoadTimer = new Timer(50, new MemoryLoadTimer());
		this.contentLoadTimer = new Timer(50, new ContentLoadTimer());
		this.tableLoadTimer = new Timer(0, new TableLoadTimer());
	}

	public void start() {
		tableLoadTimer.start();
	}

	public void destroy() {
		stopTimer(tableLoadTimer);
		stopTimer(memoryLoadTimer);
		stopTimer(contentLoadTimer);
		stopTimer(walkTimer);
		stopTimer(waitTimer);
		stopTimer(waitTimer2);
	}

	public void stopTimer(Timer timer) {
		if (timer != null && timer.isRunning()) {
			timer.stop();
		}
	}

	protected void routerInit() {
		for (int i = 0; i < contentRouter.length; i++) {
			if (i == 0) {
				contentRouter[0][0] = contents[0];
				reserve[0] = true;
				reserveCount++;
			} else {
				contentRouter[i] = contentRouter[i - 1].clone();
				int count = 0;
				for (int j = 0; j < contentRouter[i].length; j++) {
					if (contentRouter[i][j] == null) {
						reserve[i] = true;
						reserveCount++;
						contentRouter[i][j] = contents[i];
						break;
					} else if (contentRouter[i][j].getContent() == contents[i].getContent()) {
						reserve[i] = false;
						break;
					} else {
						++count;
					}
				}
				if (count == pageNumInMemory) {
					reserve[i] = true;
					reserveCount++;
					arithmetic(i);
				}
			}
		}
	}

	protected abstract void arithmetic(int i);

	private void pagesInit() {
		Point storagePosition = getStoragePosition();
		for (int i = 0; i < pages.length; i++) {
			PageContent content = pages[i];
			content.setSize(20);
			content.setInitX(storagePosition.x + 20 + (i % 3) * 30);
			content.setInitY(storagePosition.y + 10 + (i / 3) * content.getSize() + 50);
		}
	}

	private void positionInit() {
		recordTablePositionX = (WIDTH - perUnitWidth * contents.length) >> 1;
		recordTablePositionY = perUnitHeight;
		animationY = recordTablePositionY;
	}

	/**
	 * @return 该抽象类负责加载场景, 返回是否加载完毕
	 */
	protected boolean isSceneLoaded() {
		return tableLoaded && memoryLoaded;
	}

	public boolean isContentLoaded() {
		return contentLoaded;
	}

	public void setStart() {
		this.waitTimer.start();
	}

	/**
	 * 返回表格某个空格的位置
	 *
	 * @param time 第 time 次的历史记录
	 * @param n    该次的第 n 个内存单位
	 * @return 返回的点对象
	 */
	protected Point getTableCellPosition(int time, int n) {
		if (time < 0 || time > contents.length || n < 0 || n > pageNumInMemory)
			return null;
		return new Point(recordTablePositionX + perUnitWidth * time + 10, recordTablePositionY + perUnitHeight * n + 20);
	}

	protected Point getStoragePosition() {
		return new Point(recordTablePositionX + (perUnitWidth << 1) + 170,
				recordTablePositionY + perUnitHeight * pageNumInMemory + (perUnitHeight >> 1) + 30);
	}

	protected Point getCurrentPosition(int n) {
		if (n < 0 || n >= pageNumInMemory) {
			return null;
		}
		return new Point(
				recordTablePositionX + 133,
				recordTablePositionY + pageNumInMemory * perUnitHeight + 35 + (perUnitHeight << 1) * n
		);
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		paintTableBlock(g2);

		if (!tableLoaded) {
			paintTableAnimation(g2);
		} else {
			paintMemoryBlock(g2);
		}

		if (isSceneLoaded()) {
			paintHistory(g2);
			paintContents(g2);
			if (finished) {
				g2.drawString("页面置换数：" + reserveCount, WIDTH - (recordTablePositionX << 1),
						recordTablePositionY + pageNumInMemory * perUnitHeight + 20);
			}
		}
		g2.dispose();
	}

	private void paintContents(Graphics2D g2) {
		g2.setColor(new Color(0, 0, 0, alphaContent));

		g2.setFont(new Font("Arial", Font.PLAIN, 14));
		Point point = getStoragePosition();
		g2.drawString("虚拟内存：", point.x + 20, point.y + 50);
		for (PageContent page : pages) {
			g2.setFont(new Font("Arial", Font.PLAIN, page.getSize()));
			g2.drawString(String.valueOf(page.getContent()), page.getX(), page.getY() + 30);
		}

		float x = recordTablePositionX, y = recordTablePositionY + perUnitHeight * pageNumInMemory + 20;
		g2.setFont(new Font("Arial", Font.PLAIN, 14));
		g2.drawString("页", x, y);
		g2.drawString("面", x, y + 20);
		g2.drawString("申", x, y + 40);
		g2.drawString("请", x, y + 60);
		g2.drawString("序", x, y + 80);
		g2.drawString("列", x, y + 100);
		g2.drawString("：", x, y + 120);

		g2.setFont(new Font("Arial", Font.PLAIN, 14));
		for (int i = 0; i < contents.length; i++) {
			PageContent content = contents[i];
			if (i == contentIndex) {
				g2.setColor(new Color(red, 0, 0, alphaContent));
				x += animation;
			} else {
				g2.setColor(new Color(0, 0, 0, alphaContent));
				x = recordTablePositionX;
			}
			g2.drawString(String.valueOf(content.getContent()), x + 25, y + 10 + 16 * i);
		}
	}

	private void paintHistory(Graphics2D g2) {
		g2.setFont(new Font("Arial", Font.PLAIN, 12));
		for (int i = 0; i <= contentIndex; i++) {
			if (i == contentIndex) {
				g2.setColor(new Color(0, 0, 0, alphaHistory));
			} else {
				g2.setColor(Color.BLACK);
			}
			for (int j = 0; j < contentHistory[i].length; j++) {
				if (i == contentIndex) {
					g2.setColor(reserve[i] ? new Color(255, 0, 0, alphaHistory) : new Color(0, 0, 0, alphaHistory));
				} else {
					g2.setColor(reserve[i] ? Color.RED : Color.BLACK);
				}
				if (contentHistory[i][j] != null) {
					Point point = getTableCellPosition(i, j);
					g2.drawString(String.valueOf(contentHistory[i][j].getContent()), point.x, point.y);
				}
			}
		}
	}

	private void paintMemoryBlock(Graphics2D g2) {
		int x = recordTablePositionX + 30,
				y = recordTablePositionY + perUnitHeight * pageNumInMemory + 30;
		g2.setColor(new Color(143, 166, 255, alphaStorage));
		int offset = 80;
		g2.fillRect(
				x + offset, y,
				perUnitWidth << 1,
				(perUnitHeight << 1) * pageNumInMemory
		);
		g2.setColor(Color.WHITE);
		for (int i = 1; i < pageNumInMemory; i++) {
			g2.drawLine(
					x + offset, y + (perUnitHeight << 1) * i,
					x + (perUnitWidth << 1) + offset,
					y + (perUnitHeight << 1) * i
			);
		}
		int indent = 140;
		Shape up = new Ellipse2D.Double(
				x + (perUnitWidth << 1) + indent, y,
				perUnitWidth << 2,
				perUnitHeight
		);
		Shape down = new Ellipse2D.Double(
				x + (perUnitWidth << 1) + indent,
				y + (perUnitHeight << 1) * pageNumInMemory - 30,
				perUnitWidth << 2,
				perUnitHeight
		);
		Shape rect = new Rectangle2D.Double(
				x + (perUnitWidth << 1) + indent,
				y + (perUnitHeight >> 1),
				perUnitWidth << 2,
				(perUnitHeight << 1) * pageNumInMemory - 30
		);
		Area areaUp = new Area(up);
		Area areaDown = new Area(down);
		Area areaRect = new Area(rect);
		areaRect.subtract(areaUp);
		areaRect.add(areaDown);

		g2.setColor(new Color(193, 208, 255, alphaStorage));
		g2.fill(areaUp);
		g2.setColor(new Color(143, 166, 255, alphaStorage));
		g2.fill(areaRect);
	}

	private void paintTableAnimation(Graphics2D g2) {
		if (animationX != 0) {g2.setColor(index % 2 == 0 ? Color.CYAN : Color.LIGHT_GRAY);
			g2.fillRect(animationX, animationY, perUnitWidth, perUnitHeight * pageNumInMemory);
			g2.setColor(Color.WHITE);
			for (int j = 0; j < pageNumInMemory; j++) {
				g2.drawLine(
						animationX,
						animationY + perUnitHeight * (j + 1),
						animationX + perUnitHeight,
						animationY + perUnitHeight * (j + 1)
				);
			}
		}
		g2.setColor(Color.BLACK);
	}

	private void paintTableBlock(Graphics2D g2) {

		for (int i = 0; i < index; i++) {
			g2.setColor(i % 2 == 0 ? Color.CYAN : Color.LIGHT_GRAY);
			g2.fillRect(
					recordTablePositionX + perUnitWidth * i,
					recordTablePositionY,
					perUnitWidth,
					perUnitHeight * pageNumInMemory
			);
			g2.setColor(Color.WHITE);
			for (int j = 0; j < pageNumInMemory; j++) {
				g2.drawLine(
						recordTablePositionX + perUnitWidth * i,
						recordTablePositionY + perUnitHeight * (j + 1),
						recordTablePositionX + perUnitHeight * (i + 1),
						recordTablePositionY + perUnitHeight * (j + 1)
				);
			}
		}
		g2.setColor(Color.BLACK);
	}

	private class TableLoadTimer implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			animationX = animationX == 0 ? WIDTH : animationX - 2;
			animationX = index == contents.length ? -100 : animationX;
			if (animationX <= (recordTablePositionX + perUnitWidth * index)) {
				++index;
				animationX = 0;
				if (index >= contents.length + 1) {
					tableLoadTimer.stop();
					tableLoaded = true;
					memoryLoadTimer.start();
				}
			}
			if (index > contents.length) --index;
			repaint(
					(animationX == 0 ? (recordTablePositionX + perUnitWidth * (index - 1)) : animationX),
					animationY,
					perUnitWidth + 5,
					perUnitHeight * pageNumInMemory + 10
			);
		}
	}

	private class MemoryLoadTimer implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			alphaStorage += 10;
			if (alphaStorage >= 255) {
				alphaStorage = 255;
				memoryLoaded = true;
				memoryLoadTimer.stop();
				contentLoadTimer.start();
			}
			repaint(
					recordTablePositionX,
					recordTablePositionY + perUnitHeight * pageNumInMemory + 20,
					(perUnitWidth << 1) * contents.length + 1,
					(perUnitHeight << 1) * pageNumInMemory + 30
			);
		}
	}

	private class ContentLoadTimer implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			alphaContent += 10;
			if (alphaContent >= 255) {
				alphaContent = 255;
				contentLoaded = true;
				contentLoadTimer.stop();
			}
			Point p = getStoragePosition();
			repaint(p.x, p.y, (perUnitWidth << 2), (perUnitHeight << 1) * pageNumInMemory);
			repaint(recordTablePositionX, recordTablePositionY * pageNumInMemory + 20, 20, 80);
		}
	}

	private class WaitTimer implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			red += 20;
			animation += 0.5;
			if (animation >= 10) {
				animation = 10;
			}
			if (red >= 255) {
				red = 255;
			}
			if (red == 255 && animation == 10) {
				waitTimer.stop();
				contentInit();
				alphaHistory = 0;
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				walkTimer.start();
			}
			repaint(recordTablePositionX + 25, recordTablePositionY * pageNumInMemory + 40 + 16 * contentIndex,
					20, 20);
		}

		private void contentInit() {
			for (int i = 0; i < pageNumInMemory; i++) {
				contentCurrent[i] = contentRouter[contentIndex][i];
				if (contentCurrent[i] != null) {
					Point point = getCurrentPosition(i);
					contentCurrent[i].setStepX((contentCurrent[i].getX() - (float) point.x) / 100);
					contentCurrent[i].setStepY((contentCurrent[i].getY() - (float) point.y) / 100);
				}
			}
			if (contentOut[contentIndex] != null) {
				contentOut[contentIndex].setStepX((contentOut[contentIndex].getX() - contentOut[contentIndex].getInitX()) / 100);
				contentOut[contentIndex].setStepY((contentOut[contentIndex].getY() - contentOut[contentIndex].getInitY()) / 100);
			}
		}
	}

	private class WalkTimer implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			boolean b = true;
			for (int i = 0; i < contentCurrent.length; i++) {
				b &= move(contentCurrent[i], getCurrentPosition(i));
			}
			if (contentOut[contentIndex] != null) {
				b &= move(contentOut[contentIndex], new Point((int)contentOut[contentIndex].getInitX(),
						(int)contentOut[contentIndex].getInitY()));
			}
			if (b) {
				System.arraycopy(contentCurrent, 0, contentHistory[contentIndex], 0, contentHistory[contentIndex].length);
				walkTimer.stop();
				waitTimer2.start();
			}
			repaint();
		}

		private boolean move(PageContent pageContent, Point point) {
			if (pageContent != null) {
				pageContent.setX(pageContent.getX() - pageContent.getStepX());
				pageContent.setY(pageContent.getY() - pageContent.getStepY());
				if (pageContent.getStepX() > 0 ?
						pageContent.getX() < point.x :
						pageContent.getX() > point.x) pageContent.setX(point.x);
				if (pageContent.getStepY() > 0 ?
						pageContent.getY() < point.y :
						pageContent.getY() > point.y) pageContent.setY(point.y);
				return pageContent.getX() == point.x && pageContent.getY() == point.y;
			}
			return true;
		}
	}

	private class WaitTimer2 implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			red -= 20;
			animation -= 0.5;
			alphaHistory += 20;
			if (red <= 0) {
				red = 0;
			}
			if (animation < 0) {
				animation = 0;
			}
			if (alphaHistory >= 255) {
				alphaHistory = 255;
			}
			if (red == 0 && alphaHistory == 255 && animation == 0) {
				waitTimer2.stop();
				if (contentIndex == 11) {
					finished = true;
					repaint();
					return;
				}
				++contentIndex;
				waitTimer.start();
			}
			repaint();
		}
	}
}
